/* *****************************************************************
 *
 * SmallDesktopDisplay
 *    小型桌面显示器
 *
 * 原  作  者：Misaka
 * 修      改：微车游
 * 再次  修改：丘山鹤
 * 讨  论  群：811058758、887171863、720661626
 * 创 建 日 期：2021.07.19
 * 最后更改日期：2022.1.17
 *
 *
 * 引 脚 分 配：SCK   GPIO14
 *              MOSI  GPIO13
 *              RES   GPIO2
 *              DC    GPIO0
 *              LCDBL GPIO5
 *
 *
 *    感谢群友 @你别失望  提醒发现WiFi保存后无法重置的问题，目前已解决。详情查看更改说明！
 * *****************************************************************/

/* *****************************************************************
 *  库文件、头文件
 * *****************************************************************/
#include <WiFi.h>
#include <WebServer.h>
#include <DNSServer.h>
#include <TFT_eSPI.h>
#include <SPI.h>
#include <TJpg_Decoder.h>
#include <EEPROM.h>                 //内存
#include <Thread.h>                 //协程
#include <ThreadController.h>
#include <StaticThreadController.h> //协程控制
#include "TimeLib.h"
#include <FS.h>
#include <LittleFS.h>

#include "config.h"                  //配置文件
#include "weatherNum/weatherNum.h"   //天气图库
#include "Animate/Animate.h"         //动画模块
#include "WifiReFlash/wifiReFlash.h" //WIFI功能模块

#if WM_EN
#include <WiFiManager.h>
WiFiManager wm; // global wm instance
#endif

/* *****************************************************************
 *  字库、图片库
 * *****************************************************************/
#include "font/ZdyLwFont_20.h"  //字体库
#include "font/timeClockFont.h" //字体库
#include "img/temperature.h"    //温度图标
#include "img/humidity.h"       //湿度图标
#include "MyEEPROM/MyEEPROM.h"  // EEPROM操作

/* *****************************************************************
 *  全局变量
 * *****************************************************************/
static TimerHandle_t reflashTimeTimer_Handle = NULL;
static TimerHandle_t reflashBannerTimer_Handle = NULL;
static TimerHandle_t reflashAnimateTimer_Handle = NULL;
static TimerHandle_t triggerUpdateWeatherTimer_Handle = NULL;
static TimerHandle_t triggerSynchronisedTimer_Handle = NULL;
static TimerHandle_t synchroniseTimeOrUpdateWeatherTimer_Handle = NULL;
static TimerHandle_t serialSetTimer_Handle = NULL;


MyEEPROM myEEPROM;
WifiReFlash wifiReFlash;
WeatherNum wrat;

// LCD屏幕相关设置
TFT_eSPI tft = TFT_eSPI(); // 引脚请自行配置tft_espi库中的 User_Setup.h文件
TFT_eSprite clk = TFT_eSprite(&tft);
WeatherInfoStructure weatherInfo; /* 天气信息 */
uint8_t selectCityCodeSource = 1; // 城市代码来源选择：0，手动设置；1，根据ip获取
String cityCode = "101090609";    //天气城市代码

uint8_t loadNum = 6;            /* 开机加载进度 */
uint8_t isUpdateWeather = 0;    /* 更新天气标志位*/
uint8_t isSynchronisedTime = 0; /* 同步时间标志位 */
uint8_t currentIndex = 0;
String scrollText[6];
uint8_t Hour_sign = 60;
uint8_t Minute_sign = 60;
uint8_t Second_sign = 60;
const uint8_t *Animate_value; //指向关键帧的指针
uint32_t Animate_size;        //指向关键帧大小的指针
const char* imgId;              // 文件名
const uint8_t *weatherCode;
uint32_t weatherSize;
const uint32_t localPort = 8000; // wifi连接UDP设置参数
String SMOD = ""; // 串口功能代码

/* *****************************************************************
 *  函数声明
 * *****************************************************************/
String week();
bool tftOutput(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap);
void loading(uint8_t delayTime);
void humidityWin(uint8_t humiNum);
void tempWin(int8_t temperature);
void cityNameWin(String cityName);
void airQualityWin(uint16_t pm25Value);
void serialSet();
void webWin();
void webConfig();
String getParam(String name);
void saveParamCallback();
void weatherData(WeatherInfoStructure *weatherInfo);
void scrollBanner();
void drawLineFont(uint32_t _x, uint32_t _y, uint32_t _num, uint32_t _size, uint32_t _color);
void digitalClockDisplay();
void reflashTime();
void triggerUpdateWeather();
void triggerSynchronisedTime();
void synchronisedTimeoOrUpdateWeather();
void refreshAnimatedImage();

Thread reflashTimeThread = Thread();    //创建时间更新函数线程
Thread reflashBannerThread = Thread();  //创建副标题切换线程
Thread reflashAnimateThread = Thread(); //创建动画绘制线程
Thread triggerUpdateWeatherThread = Thread();           //创建同步线程
Thread triggerSynchronisedTimeThread = Thread();        //创建同步线程
Thread synchroniseTimeOrUpdateWeatherThread = Thread(); //创建同步线程
Thread serialSetThread = Thread();                      //创建串口线程
ThreadController controller = ThreadController();       //创建协程池

/* *****************************************************************
 *  函数实现
 * *****************************************************************/

/*
 * 获取星期几
 */
String week()
{
    // char无法表示中文，在此使用String
    String wk[7] = {"日", "一", "二", "三", "四", "五", "六"};
    return "周" + wk[weekday() - 1];
}

/*
 * TFT屏幕显示图片
 *
 */
bool tftOutput(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap)
{
    if (y >= tft.height())
        return 0;
    tft.pushImage(x, y, w, h, bitmap);
    // Return 1 to decode next block
    return 1;
}

/*
 * 开机进度条函数
 * @param delayTime 延时
 */
void loading(uint8_t delayTime)
{
    clk.createSprite(200, 20);                       //创建窗口
    clk.fillSprite(BGCOLOR);                         //填充率
    clk.drawRoundRect(0, 0, 200, 16, 8, 0xFFFF);     //空心圆角矩形
    clk.fillRoundRect(3, 3, loadNum, 10, 5, 0xFFFF); //实心圆角矩形

    clk.pushSprite(20, 120); //窗口位置
    clk.deleteSprite();
    loadNum += 1;
    delay(delayTime);
}

/*
 * 湿度显示函数
 * https://www.docin.com/p-63387501.html
 * @param humiNum 湿度
 */
void humidityWin(uint8_t humiNum)
{
    uint16_t humiCol = 0xffff; //湿度显示颜色

    if (humiNum > 90)
        humiCol = clk.color565(0, 15, 80);
    else if (humiNum > 80)
        humiCol = clk.color565(7, 30, 120);
    else if (humiNum > 70)
        humiCol = clk.color565(17, 44, 144);
    else if (humiNum > 60)
        humiCol = clk.color565(28, 59, 169);
    else if (humiNum > 50)
        humiCol = clk.color565(43, 92, 194);
    else if (humiNum > 40)
        humiCol = clk.color565(59, 126, 219);
    else if (humiNum > 50)
        humiCol = clk.color565(107, 157, 225);
    else if (humiNum > 20)
        humiCol = clk.color565(155, 188, 232);
    else if (humiNum > 10)
        humiCol = clk.color565(153, 211, 202);
    else
        humiCol = clk.color565(151, 232, 173);

    clk.createSprite(113, 24);
    clk.fillSprite(BGCOLOR);
    clk.fillRoundRect(1, 10, humiNum / 2, 4, 2, humiCol); //实心圆角矩形
    clk.drawRoundRect(0, 9, 52, 6, 3, 0xFFFF);            //空心圆角矩形  起始位x,y,长度，宽度，圆弧半径，颜色
    clk.drawString(String(humiNum) + "%", 83, 13);
    clk.pushSprite(45, 214);
    clk.deleteSprite();
}

/*
 * 温度显示函数
 * @param temperature 温度
 */
void tempWin(int8_t temp)
{
    uint16_t tempCol = 0xffff; //温度显示颜色
    uint8_t tempLen;           // 温度条长度

    if ((temp + 10) < 0)
        tempLen = 0;
    else if ((temp + 10) > 50)
        tempLen = 50;
    else
        tempLen = temp + 10;

    if (temp < -30)
        tempCol = clk.color565(2, 12, 100);
    else if (temp < -28)
        tempCol = clk.color565(7, 30, 120);
    else if (temp < -26)
        tempCol = clk.color565(17, 49, 139);
    else if (temp < -24)
        tempCol = clk.color565(27, 68, 159);
    else if (temp < -22)
        tempCol = clk.color565(38, 87, 179);
    else if (temp < -20)
        tempCol = clk.color565(48, 106, 199);
    else if (temp < -18)
        tempCol = clk.color565(59, 126, 219);
    else if (temp < -16)
        tempCol = clk.color565(78, 138, 221);
    else if (temp < -14)
        tempCol = clk.color565(97, 150, 224);
    else if (temp < -12)
        tempCol = clk.color565(116, 163, 226);
    else if (temp < -10)
        tempCol = clk.color565(135, 175, 229);
    else if (temp < -8)
        tempCol = clk.color565(155, 188, 232);
    else if (temp < -6)
        tempCol = clk.color565(154, 196, 220);
    else if (temp < -4)
        tempCol = clk.color565(153, 205, 208);
    else if (temp < -2)
        tempCol = clk.color565(152, 214, 196);
    else if (temp < 0)
        tempCol = clk.color565(151, 232, 173);
    else if (temp < 2)
        tempCol = clk.color565(215, 222, 126);
    else if (temp < 4)
        tempCol = clk.color565(234, 219, 122);
    else if (temp < 6)
        tempCol = clk.color565(244, 217, 99);
    else if (temp < 8)
        tempCol = clk.color565(250, 204, 79);
    else if (temp < 10)
        tempCol = clk.color565(247, 180, 0);
    else if (temp < 12)
        tempCol = clk.color565(242, 155, 0);
    else if (temp < 14)
        tempCol = clk.color565(241, 147, 3);
    else if (temp < 16)
        tempCol = clk.color565(240, 132, 10);
    else if (temp < 18)
        tempCol = clk.color565(239, 117, 17);
    else if (temp < 20)
        tempCol = clk.color565(238, 102, 24);
    else if (temp < 22)
        tempCol = clk.color565(238, 88, 31);
    else if (temp < 24)
        tempCol = clk.color565(231, 75, 26);
    else if (temp < 26)
        tempCol = clk.color565(224, 63, 22);
    else if (temp < 28)
        tempCol = clk.color565(217, 51, 18);
    else if (temp < 30)
        tempCol = clk.color565(208, 36, 14);
    else if (temp < 32)
        tempCol = clk.color565(194, 0, 3);
    else if (temp < 34)
        tempCol = clk.color565(181, 1, 9);
    else if (temp < 35)
        tempCol = clk.color565(169, 2, 16);
    else if (temp < 37)
        tempCol = clk.color565(138, 5, 25);
    else if (temp < 40)
        tempCol = clk.color565(111, 0, 21);
    else
        tempCol = clk.color565(80, 0, 15);

    clk.loadFont(ZdyLwFont_20);
    clk.createSprite(113, 24);
    clk.fillSprite(BGCOLOR);
    clk.fillRoundRect(1, 10, tempLen, 4, 2, tempCol); //实心圆角矩形
    clk.drawRoundRect(0, 9, 52, 6, 3, 0xFFFF);        //空心圆角矩形  起始位x,y,长度，宽度，圆弧半径，颜色
    clk.setTextDatum(CC_DATUM);
    clk.setTextColor(TFT_WHITE, BGCOLOR);
    clk.drawString(String(temp) + "℃", 83, 13);
    clk.pushSprite(45, 184); //窗口位置
    clk.deleteSprite();
}

/*
 * 城市名称显示函数
 * @param cityName 城市名
 */
void cityNameWin(String cityName)
{
    clk.createSprite(94, 24);
    clk.fillSprite(BGCOLOR);
    clk.setTextDatum(CC_DATUM);
    clk.setTextColor(TFT_WHITE, BGCOLOR);
    clk.drawString(cityName, 44, 13);
    clk.pushSprite(15, 18);
    clk.deleteSprite();
}

/*
 * 空气质量显示函数
 * https://www.mee.gov.cn/ywgz/fgbz/bz/bzwb/jcffbz/201203/t20120302_224166.shtml
 * @param pm25Value pm2.5值
 */
void airQualityWin(uint16_t pm25Value)
{

    uint16_t color; //优
    String aqiTxt = "";

    if (pm25Value >= 300)
    {
        // color = clk.color565(136, 11, 32); //严重
        color = clk.color565(126, 0, 35);
        aqiTxt = "严重";
    }
    else if ((pm25Value >= 200) && (pm25Value < 300))
    {
        // color = clk.color565(136, 11, 32); //重度
        color = clk.color565(153, 0, 76);
        aqiTxt = "重度";
    }
    else if ((pm25Value >= 150) && (pm25Value < 200))
    {
        // color = clk.color565(186, 55, 121); //中度
        color = clk.color565(255, 0, 0);
        aqiTxt = "中度";
    }
    else if ((pm25Value >= 100) && (pm25Value < 150))
    {
        // color = clk.color565(242, 159, 57); //轻度
        color = clk.color565(255, 126, 0);
        aqiTxt = "轻度";
    }
    else if ((pm25Value >= 50) && (pm25Value < 100))
    {
        // color = clk.color565(247, 219, 100); //良
        color = clk.color565(255, 255, 0);
        aqiTxt = "良";
    }
    else
    {
        // color = clk.color565(156, 202, 127); //优
        color = clk.color565(0, 228, 0);
        aqiTxt = "优";
    }

    clk.createSprite(56, 24);
    clk.fillSprite(BGCOLOR);
    clk.fillRoundRect(0, 0, 50, 24, 4, color);
    clk.setTextDatum(CC_DATUM);
    clk.setTextColor(0x0000);
    clk.drawString(aqiTxt, 25, 13);
    clk.pushSprite(104, 18);
    clk.deleteSprite();
}

/*
 * 串口调试设置函数
 */
void serialSet()
{
    String incomingByte = "";
    if (Serial.available() > 0)
    {
        while (Serial.available() > 0) //监测串口缓存，当有数据输入时，循环赋值给incomingByte
        {
            incomingByte += char(Serial.read()); //读取单个字符值，转换为字符，并按顺序一个个赋值给incomingByte
            delay(2);                            //不能省略，因为读取缓冲区数据需要时间
        }


        if (SMOD == "0x01") //设置2地址设置
        {
            uint32_t CityC = atoi(incomingByte.c_str()); // int n = atoi(xxx.c_str());//String转int
            if (((CityC >= 101000000) && (CityC <= 102000000)) || (CityC == 0))// 判断是否合法
            {
                myEEPROM.saveCityConfig(CityC);
                cityCode = String(myEEPROM.readCityConfig());

                if (cityCode == "0")
                {
                    Serial.println(F("城市代码调整为：自动"));
                    cityCode = wifiReFlash.getCityCode(); //获取城市代码
                }
                else
                {
                    Serial.printf("城市代码调整为：%s\r\n", cityCode.c_str());
                }
                wifiReFlash.getCityWeather(cityCode, &weatherInfo); //更新城市天气
            }
            else
                Serial.println(F("城市调整错误，请输入9位城市代码，自动获取请输入0"));
            SMOD = "";
        }
        else if (SMOD == "0x02") //设置1亮度设置
        {
            int16_t LCDBL = atoi(incomingByte.c_str()); // int n = atoi(xxx.c_str());//String转int
            if (LCDBL > 0 && LCDBL <= 255)
            {
                myEEPROM.saveLCDBacklightConfig(LCDBL);
                // myEEPROM.LCD_BL_PWM = myEEPROM.readLCDBacklightConfig();
                analogWrite(LCD_BL_PIN, 255 - LCDBL);
                Serial.printf("亮度调整为：%d\r\n", LCDBL);
            }
            else
            {
                Serial.println(F("亮度设置错误，亮度值范围1-255"));
            }
            SMOD = "";
        }
        else if (SMOD == "0x03") //设置3屏幕显示方向
        {
            int8_t RoSet = atoi(incomingByte.c_str());
            if (RoSet >= 0 && RoSet <= 3)
            {
                if (RoSet == myEEPROM.readLCDRotationConfig())
                {
                    Serial.println(F("当前屏幕方向已经是正确方向"));
                }
                else
                {

                    myEEPROM.saveLCDRotationConfig(RoSet);
                    // tft.setRotation(myEEPROM.LCD_Rotation);
                    Serial.printf("屏幕方向设置为：%d\r\n，重启以生效\r\n", RoSet);
                    delay(1000);
                    ESP.restart();
                }
            }
            else
            {
                Serial.println(F("屏幕方向值错误，请输入0-3内的值"));
            }
            SMOD = "";
        }
        else if (SMOD == "0x04") //设置天气更新时间
        {
            int8_t wtup = atoi(incomingByte.c_str()); // int n = atoi(xxx.c_str());//String转int
            if (wtup >= 1 && wtup <= 60)
            {
                myEEPROM.updateWeatherTime = wtup;
                triggerUpdateWeatherThread.setInterval(myEEPROM.updateWeatherTime * 60 * TMS);
                Serial.printf("天气更新间隔更改为：%d分钟\r\n", myEEPROM.updateWeatherTime);
                myEEPROM.saveUpdateWeatherTimeConfig(myEEPROM.updateWeatherTime);
            }
            else
                Serial.println(F("天气更新间隔错误，请重新设置（1-60）"));
            SMOD = "";
        }
        else if (SMOD == "0x05") //设置滚动栏滚动间隔
        {
            int8_t scroll = atoi(incomingByte.c_str());
            if (scroll > 0 && scroll <= 10)
            {
                myEEPROM.scrollInterval = scroll;
                reflashBannerThread.setInterval(myEEPROM.scrollInterval * TMS);
                Serial.printf("滚动栏滚动切换改为：%d秒\r\n", myEEPROM.scrollInterval);
                myEEPROM.saveScrollIntervalConfig(myEEPROM.scrollInterval);
            }
            else
                Serial.println(F("滚动条滚动时间间隔错误，请重新设置（1-10）"));
            SMOD = "";
        }
        else if (SMOD == "0x06") //设置动画帧率
        {
            int8_t anim = atoi(incomingByte.c_str());
            if (anim > 0 && anim <= 60)
            {
                myEEPROM.animateFrameRate = anim;
                reflashAnimateThread.setInterval(TMS / myEEPROM.animateFrameRate);
                Serial.printf("动画帧率更改为：%d\r\nfps", myEEPROM.animateFrameRate);
                myEEPROM.saveAnimateFrameRateConfig(myEEPROM.animateFrameRate);
            }
            else
                Serial.println(F("动画帧率错误，请重新设置（1-60）"));
            SMOD = "";
        }
        else
        {
            SMOD = incomingByte;
            delay(2);
            if (SMOD == "0x01")
            {
                Serial.printf("当前城市为：%d\r\n", myEEPROM.readCityConfig());
                Serial.println(F("请输入9位城市代码，IP自动获取请输入0"));
            }
                        else if (SMOD == "0x02")
            {
                Serial.printf("当前亮度为：%d\r\n", myEEPROM.readLCDBacklightConfig());
                Serial.println(F("请输入亮度值，范围1-255"));
            }
            else if (SMOD == "0x03")
            {
                Serial.printf("当前屏幕方向：%d\r\n", myEEPROM.readLCDRotationConfig());
                Serial.println(F("请输入屏幕方向：\r\n0-USB接口朝下\r\n1-USB接口朝右\r\n2-USB接口朝上\r\n3-USB接口朝左"));
            }
            else if (SMOD == "0x04")
            {
                Serial.printf("当前天气更新间隔：%d分钟\r\n", myEEPROM.readUpadteWeatherTimeConfig());
                Serial.println(F("请输入天气更新时间（1-60）分钟"));
            }
            else if (SMOD == "0x05")
            {
                Serial.printf("当前滚动栏切换间隔时间：%d秒\r\n", myEEPROM.readScrollIntervalConfig());
                Serial.println(F("请输入滚动条滚动间隔时间（1-60）秒"));
            }
            else if (SMOD == "0x06")
            {
                Serial.printf("当前动画帧率：%d\r\nfps", myEEPROM.readAnimateFrameRateConfig());
                Serial.println(F("请输入动画帧率（1-60）"));
            }
            else if (SMOD == "0x07")
            {
                Serial.println(F("重置WiFi设置中......"));
                myEEPROM.deletewificonfig();
                Serial.println(F("重置WiFi成功"));
                ESP.restart();
            }
            else if (SMOD == "0x08")
            {
                myEEPROM.wifiConf = myEEPROM.readWIFIConfig();
                SMOD = "";

                Serial.printf("参数信息：\r\n城市代码：%s\r\n屏幕亮度：%d\r\n屏幕方向：%d\r\n天气更新间隔：%d分钟\r\n"
                              "滚动栏滚动间隔：%d秒\r\n动画帧率：%dFPS\r\nWIFI：%s @%s\r\n",
                              cityCode.c_str(),
                              myEEPROM.readLCDBacklightConfig(),
                              myEEPROM.readLCDRotationConfig(),
                              myEEPROM.readUpadteWeatherTimeConfig(),
                              myEEPROM.readScrollIntervalConfig(),
                              myEEPROM.readAnimateFrameRateConfig(),
                              myEEPROM.wifiConf.stassid,
                              myEEPROM.wifiConf.stapsw);
            }
            else
            {
                Serial.print(F("\r\n请输入需要修改的代码：\r\n"
                               "设置城市代码        0x01\r\n"
                               "设置屏幕亮度        0x02\r\n"
                               "设置屏幕方向(重启)  0x03\r\n"
                               "设置天气更新间隔    0x04\r\n"
                               "设置滚动栏滚动间隔  0x05\r\n"
                               "设置动画帧率        0x06\r\n"
                               "重置WiFi(重启)      0x07\r\n"
                               "显示参数信息        0x08\r\n"));
            }
        }
    }
}

#if WM_EN
/*
 *WEB配网LCD显示函数
 */
void webWin()
{
    clk.createSprite(200, 60);
    clk.fillSprite(BGCOLOR);

    clk.setTextDatum(CC_DATUM);
    clk.setTextColor(TFT_GREEN, 0x0000);
    clk.drawString("Connect AP to Config", 100, 10, 2);
    clk.drawString("SSID:", 45, 40, 2);
    clk.setTextColor(TFT_WHITE, 0x0000);
    clk.drawString("AutoConnectAP", 125, 40, 2);
    clk.pushSprite(20, 50); //窗口位置
    clk.deleteSprite();

    clk.createSprite(200, 16); // 创建200*50的窗口
    clk.fillSprite(BGCOLOR);
    clk.setTextDatum(MC_DATUM); // 设置居中对齐
    clk.setTextColor(TFT_RED, BGCOLOR);
    clk.drawString("Failed to Connect to WiFi", 100, 8, 2);
    // clk.drawString("......", 100, 35);
    clk.pushSprite(20, 152); // 设置窗口位置
    clk.deleteSprite();
}

/*
 * WEB配网函数
 */
void webConfig()
{
    WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP

    delay(3000);
    wm.resetSettings(); // wipe settings

    // add a custom input field
    // int customFieldLength = 40;

    // new (&custom_field) WiFiManagerParameter("customfieldid", "Custom Field Label", "Custom Field Value", customFieldLength,"placeholder=\"Custom Field Placeholder\"");

    // test custom html input type(checkbox)
    //  new (&custom_field) WiFiManagerParameter("customfieldid", "Custom Field Label", "Custom Field Value", customFieldLength,"placeholder=\"Custom Field Placeholder\" type=\"checkbox\""); // custom html type

    // test custom html(radio)
    // const char* custom_radio_str = "<br/><label for='customfieldid'>Custom Field Label</label><input type='radio' name='customfieldid' value='1' checked> One<br><input type='radio' name='customfieldid' value='2'> Two<br><input type='radio' name='customfieldid' value='3'> Three";
    // new (&custom_field) WiFiManagerParameter(custom_radio_str); // custom html input

    const char *set_rotation = "<label for='set_rotation'>显示方向设置</label><br>\
                              <input type='radio' name='set_rotation' value='0' checked> USB接口朝下<br>\
                              <input type='radio' name='set_rotation' value='1'> USB接口朝右<br>\
                              <input type='radio' name='set_rotation' value='2'> USB接口朝上<br>\
                              <input type='radio' name='set_rotation' value='3'> USB接口朝左<br>";
    WiFiManagerParameter custom_rot(set_rotation); // custom html input
    WiFiManagerParameter custom_bl("LCDBL", "屏幕亮度（1-255）", "10", 3);
    WiFiManagerParameter custom_weatertime("WeaterUpdateTime", "天气更新间隔（分钟）", "10", 3);
    WiFiManagerParameter custom_cc("CityCode", "城市代码（0自动获取）", "0", 9);
    WiFiManagerParameter custom_scrollInterval("scrollInterval", "滚动栏滚动间隔", "2", 3);
    WiFiManagerParameter custom_animateFrameRate("animateFrameRate", "动画帧率", "20", 3);
    WiFiManagerParameter p_lineBreak_notext("<p></p>");

    // wm.addParameter(&p_lineBreak_notext);
    // wm.addParameter(&custom_field);
    wm.addParameter(&p_lineBreak_notext);
    wm.addParameter(&custom_cc);            // 城市代码
    wm.addParameter(&p_lineBreak_notext);
    wm.addParameter(&custom_bl);            // 屏幕亮度
    wm.addParameter(&p_lineBreak_notext);
    wm.addParameter(&custom_rot);           // 屏幕方向
    wm.addParameter(&p_lineBreak_notext);
    wm.addParameter(&custom_weatertime);    // 天气更新间隔
    wm.addParameter(&p_lineBreak_notext);
    wm.addParameter(&custom_scrollInterval);       // 滚动栏滚动间隔
    wm.addParameter(&p_lineBreak_notext);
    wm.addParameter(&custom_animateFrameRate);      // 动画帧率


    wm.setSaveParamsCallback(saveParamCallback);

    // custom menu via array or vector
    //
    // menu tokens, "wifi","wifinoscan","info","param","close","sep","erase","restart","exit" (sep is seperator) (if param is in menu, params will not show up in wifi page!)
    // const char* menu[] = {"wifi","info","param","sep","restart","exit"};
    // wm.setMenu(menu,6);
    std::vector<const char *> menu = {"wifi", "restart"};
    wm.setMenu(menu);

    // set dark theme
    wm.setClass("invert");

    // set static ip
    //  wm.setSTAStaticIPConfig(IPAddress(10,0,1,99), IPAddress(10,0,1,1), IPAddress(255,255,255,0)); // set static ip,gw,sn
    //  wm.setShowStaticFields(true); // force show static ip fields
    //  wm.setShowDnsFields(true);    // force show dns field always

    // wm.setConnectTimeout(20); // how long to try to connect for before continuing
    //  wm.setConfigPortalTimeout(30); // auto close configportal after n seconds
    // wm.setCaptivePortalEnable(false); // disable captive portal redirection
    // wm.setAPClientCheck(true); // avoid timeout if client connected to softap

    // wifi scan settings
    // wm.setRemoveDuplicateAPs(false); // do not remove duplicate ap names (true)
    wm.setMinimumSignalQuality(20); // set min RSSI (percentage) to show in scans, null = 8%
    // wm.setShowInfoErase(false);      // do not show erase button on info page
    // wm.setScanDispPerc(true);       // show RSSI as percentage not graph icons
    // wm.setBreakAfterConfig(true);   // always exit configportal even if wifi save fails

    bool res;
    // res = wm.autoConnect(); // auto generated AP name from chipid
    res = wm.autoConnect("AutoConnectAP"); // anonymous ap
    //  res = wm.autoConnect("AutoConnectAP","password"); // password protected ap

    while (!res);
}

/*
 * 获取参数
 */
String getParam(String name)
{
    // read parameter from server, for customhmtl input
    // String value;
    if (wm.server->hasArg(name))
    {
        // value = wm.server->arg(name);
        return wm.server->arg(name);
    }
    else
        return "";
}

/*
 * 保存按钮回调
 */
void saveParamCallback()
{
    uint32_t tempCityCode;
#if DEBUG_EN
    Serial.println("[CALLBACK] saveParamCallback fired");
    Serial.println("PARAM customfieldid = " + getParam("customfieldid"));
    Serial.println("PARAM CityCode = " + getParam("CityCode"));
    Serial.println("PARAM LCD BackLight = " + getParam("LCDBL"));
    Serial.println("PARAM WeaterUpdateTime = " + getParam("WeaterUpdateTime"));
    Serial.println("PARAM Rotation = " + getParam("set_rotation"));
#endif
    //将从页面中获取的数据保存

    tempCityCode = getParam("CityCode").toInt();
    myEEPROM.LCD_BL_PWM = getParam("LCDBL").toInt();
    myEEPROM.LCD_Rotation = getParam("set_rotation").toInt();
    myEEPROM.updateWeatherTime = getParam("WeaterUpdateTime").toInt();
    myEEPROM.scrollInterval = getParam("scrollInterval").toInt();
    myEEPROM.animateFrameRate = getParam("animateFrameRate").toInt();

    //对获取的数据进行处理
#if DEBUG_EN
    // Serial.printf("天气更新时间调整为：");
    // Serial.println(myEEPROM.updateWeatherTime);
    // Serial.print("CityCode = ");
    // Serial.println(tempCityCode);
    // Serial.print("myEEPROM.LCD_Rotation = ");
    // Serial.println(myEEPROM.LCD_Rotation);
    // Serial.println(myEEPROM.LCD_BL_PWM);
    // Serial.printf("亮度调整为：");

    Serial.printf("天气更新时间调整为：%i\r\n\
                   CityCode = %i\r\n\
                   myEEPROM.LCD_Rotation = %i\r\n\
                   亮度调整为：%i\r\n",
                   myEEPROM.updateWeatherTime,
                   tempCityCode,
                   myEEPROM.LCD_Rotation,
                   myEEPROM.LCD_BL_PWM);
#endif

    //城市代码
    myEEPROM.saveCityConfig(tempCityCode);
    cityCode = myEEPROM.readCityConfig();

    //屏幕方向
    myEEPROM.saveLCDRotationConfig(myEEPROM.LCD_Rotation);
    tft.setRotation(myEEPROM.LCD_Rotation);
    tft.fillScreen(0x0000);

    // 屏幕亮度
    myEEPROM.saveLCDBacklightConfig(myEEPROM.LCD_BL_PWM);
    analogWrite(LCD_BL_PIN, 255 - myEEPROM.LCD_BL_PWM);

    // 天气更新时间
    myEEPROM.saveUpdateWeatherTimeConfig(myEEPROM.updateWeatherTime);
    // 滚动栏滚动间隔
    myEEPROM.saveScrollIntervalConfig(myEEPROM.scrollInterval);
    // 动画帧率
    myEEPROM.saveAnimateFrameRateConfig(myEEPROM.animateFrameRate);

    clk.createSprite(200, 16); // 创建200*50的窗口
    clk.fillSprite(BGCOLOR);
    clk.setTextDatum(MC_DATUM); // 设置居中对齐
    clk.setTextColor(TFT_GREEN, BGCOLOR);
    clk.drawString("Connecting to WiFi", 100, 8, 2);
    clk.pushSprite(20, 152); // 设置窗口位置
    clk.deleteSprite();

    loadNum = 6;
    loading(1);
}
#endif

#if !WM_EN
//微信配网函数
void SmartConfig(void)
{
    WiFi.mode(WIFI_STA); //设置STA模式
    // tft.pushImage(0, 0, 240, 240, qr);
    tft.pushImage(0, 0, 240, 240, qr);
    Serial.println("\r\nWait for Smartconfig..."); //打印log信息
    WiFi.beginSmartConfig();                       //开始SmartConfig，等待手机端发出用户名和密码
    while (1)
    {
        Serial.print(".");
        delay(100);                 // wait for a second
        if (WiFi.smartConfigDone()) //配网成功，接收到SSID和密码
        {
            Serial.println("SmartConfig Success");
            Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str());
            Serial.printf("PSW:%s\r\n", WiFi.psk().c_str());
            break;
        }
    }
    loadNum = 194;
}
#endif

/*
 * 天气信息解析，显示到屏幕上
 * @param weatherInfo 天气信息
 */
void weatherData(WeatherInfoStructure *weatherInfo)
{

    // 温度
    tempWin(weatherInfo->temperature);

    // 湿度
    humidityWin(weatherInfo->humidity);

    // 城市名称
    cityNameWin(weatherInfo->cityName);

    // PM2.5空气指数
    airQualityWin(weatherInfo->pm25Value);

    //天气图标
    wrat.setTodayWeatherAndCurrentWeatherCode(weatherInfo->todayWeatherCode, weatherInfo->weatherCode);

    /*********左上角滚动字幕**************/
    scrollText[0] = "实时天气 " + weatherInfo->weather;
    scrollText[1] = "PM2.5 " + String(weatherInfo->pm25Value);
    scrollText[2] = "风向 " + weatherInfo->windDirection + weatherInfo->windSpeed;
    scrollText[3] = "今日 " + weatherInfo->todayWeather;
    scrollText[4] = "最低温度" + String(weatherInfo->minTemperature) + "℃";
    scrollText[5] = "最高温度" + String(weatherInfo->maxTemperature) + "℃";
}

/*
 * 左上角滚动字幕实现滚动
 */
void scrollBanner()
{
    clk.createSprite(150, 30);
    clk.fillSprite(BGCOLOR);
    clk.setTextWrap(false);
    clk.setTextDatum(CC_DATUM);
    clk.setTextColor(TFT_WHITE, BGCOLOR);
    clk.drawString(*(scrollText + currentIndex), 74, 16);
    clk.pushSprite(10, 45);
    clk.deleteSprite();

    switch (currentIndex)
    {
    case 0: /* 实时天气 */
        clk.createSprite(45, 30);
        clk.fillSprite(BGCOLOR);
        clk.fillRoundRect(0, 0, 45, 24, 4, 0x962F);
        clk.setTextDatum(CC_DATUM);
        clk.setTextColor(0x0000);
        clk.drawString("实时", 22, 13);
        clk.pushSprite(182, TIME_Y);
        clk.deleteSprite();
        wrat.setWeather(&weatherCode, &weatherSize, 1);
        TJpgDec.drawJpg(170, 15, weatherCode, weatherSize);

        currentIndex += 1;
        break;
    case 3: /* 今日天气 */
        clk.createSprite(45, 30);
        clk.fillSprite(BGCOLOR);
        clk.fillRoundRect(0, 0, 45, 24, 4, 0x067A);
        clk.setTextDatum(CC_DATUM);
        clk.setTextColor(0x0000);
        clk.drawString("今日", 22, 13);
        clk.pushSprite(182, TIME_Y);
        clk.deleteSprite();
        wrat.setWeather(&weatherCode, &weatherSize, 0);
        TJpgDec.drawJpg(170, 15, weatherCode, weatherSize);
        currentIndex += 1;
        break;
    case 5:
        currentIndex = 0; //回第一个
        break;
    default:
        currentIndex += 1;
        break;
    }
}

/*
 * 用快速线方法绘制数字
 */
void drawLineFont(uint32_t _x, uint32_t _y, uint32_t _num, uint32_t _size, uint32_t _color)
{
    uint32_t fontSize;
    const LineAtom *fontOne;
    // 小号(9*14)
    if (_size == 1)
    {
        fontOne = smallLineFont[_num];
        fontSize = smallLineFont_size[_num];
        // 绘制前清理字体绘制区域
        tft.fillRect(_x, _y, 9, 14, TFT_BLACK);
    }
    // 中号(18*30)
    else if (_size == 2)
    {
        fontOne = middleLineFont[_num];
        fontSize = middleLineFont_size[_num];
        // 绘制前清理字体绘制区域
        tft.fillRect(_x, _y, 18, 30, TFT_BLACK);
    }
    // 大号(36*90)
    else if (_size == 3)
    {
        fontOne = largeLineFont[_num];
        fontSize = largeLineFont_size[_num];
        // 绘制前清理字体绘制区域
        tft.fillRect(_x, _y, 36, 60, TFT_BLACK);
    }
    else
        return;

    for (uint32_t i = 0; i < fontSize; i++)
    {
        tft.drawFastHLine(fontOne[i].xValue + _x, fontOne[i].yValue + _y, fontOne[i].lValue, _color);
    }
}

/*
 * 显示时间
 *
 */
void digitalClockDisplay()
{
    //秒针刷新
    uint8_t now_second = second(); //获取秒针
    uint8_t now_minute, now_hour;
    if (now_second != Second_sign)
    {
        drawLineFont(206, TIME_Y + 30, now_second % 10, 2, SECOND_COLOR);
        if (now_second / 10 != Second_sign / 10)
        {
            drawLineFont(184, TIME_Y + 30, now_second / 10, 2, SECOND_COLOR);
            //分钟刷新
            now_minute = minute(); //获取分钟
            if (now_minute != Minute_sign)
            {
                drawLineFont(141, TIME_Y, now_minute % 10, 3, MINUTE_COLOR);
                if (now_minute / 10 != Minute_sign / 10)
                {
                    drawLineFont(101, TIME_Y, now_minute / 10, 3, MINUTE_COLOR);
                    //小时刷新
                    now_hour = hour(); //获取小时
                    if (now_hour != Hour_sign)
                    {
                        drawLineFont(60, TIME_Y, now_hour % 10, 3, HONR_COLOR);
                        if (now_hour / 10 != Hour_sign / 10)
                        {
                            drawLineFont(20, TIME_Y, now_hour / 10, 3, HONR_COLOR);

                            /* 显示日历和星期 */
                            clk.createSprite(155, 30);
                            clk.fillSprite(BGCOLOR);
                            clk.setTextDatum(CC_DATUM);
                            clk.setTextColor(TFT_WHITE, BGCOLOR);
                            clk.drawString(String(month()) + "月" + String(day()) + "日", 49, 16);
                            clk.drawString(week(), 124, 16);
                            clk.pushSprite(5, 150);
                            clk.deleteSprite();
                        }
                        Hour_sign = now_hour;
                    }
                }
                Minute_sign = now_minute;
            }
        }
        Second_sign = now_second;
    }
}

/*
 * 屏幕刷新时间显示
 *
 */
void reflashTime()
{
    digitalClockDisplay();
}

/*
 * 触发更新天气
 */
void triggerUpdateWeather()
{
    // WiFi.forceSleepWake();
    WiFi.enableSTA(true);
    isUpdateWeather = 1;
}

/*
 * 触发同步时间
 */
void triggerSynchronisedTime()
{
    // WiFi.forceSleepWake();
    WiFi.enableSTA(true);
    isSynchronisedTime = 1;
}

/*
 * 同步时间或同步天气
 */
void synchronisedTimeoOrUpdateWeather()
{

    if (WiFi.status() == WL_CONNECTED)
    {
        if (isSynchronisedTime)
        {
           wifiReFlash.getNtpTime();
            if (wifiReFlash.ntpTime)
            {
                setTime(wifiReFlash.ntpTime);
            }
#if DEBUG_EN
            Serial.println(F("reflashTimeFromNTP()更新时间成功"));
#endif
            // WiFi.forceSleepBegin();
            WiFi.setSleep(true);
            isSynchronisedTime = 0;
        }

        if (isUpdateWeather)
        {

#if DEBUG_EN
            Serial.printf("wifi连接成功！%li\r\n", millis());
#endif
            wifiReFlash.getCityWeather(cityCode, &weatherInfo);
            if (weatherInfo.cityName != "") /* 判断weatherInfo是否获取到数据 */
            {
                weatherData(&weatherInfo);
#if DEBUG_EN
                Serial.println(F("reflashWeather()更新天气成功"));
#endif
                // WiFi.forceSleepBegin();
                WiFi.setSleep(true);
                isUpdateWeather = 0;
            }
        }
    }
}

/*
 * 右下角动画更新
 */
void refreshAnimatedImage()
{
#if ANIMATE_CHOICE
    // imgAnim(&Animate_value, &Animate_size);
    // TJpgDec.drawJpg(160, 160, Animate_value, Animate_size);
    getImgId(&imgId);
    TJpgDec.drawFsJpg(160, 160, imgId, LittleFS);
#endif
}


void setup()
{
    Serial.begin(115200);
    myEEPROM.begin(1024);
    pinMode(LCD_BL_PIN, OUTPUT);
    analogWrite(LCD_BL_PIN, 255); /* 关闭屏幕背光 */
    if (!LittleFS.begin(true))
    {
        Serial.println("LittleFs Mount Failed");
    }
    Serial.println("\r\nInitialisation done.");

    /* 从EERPOM中获取信息 */
    myEEPROM.LCD_BL_PWM = myEEPROM.readLCDBacklightConfig();
    myEEPROM.LCD_Rotation = myEEPROM.readLCDRotationConfig();
    myEEPROM.scrollInterval = myEEPROM.readScrollIntervalConfig();
    myEEPROM.animateFrameRate = myEEPROM.readAnimateFrameRateConfig();
    myEEPROM.updateWeatherTime = myEEPROM.readUpadteWeatherTimeConfig();
    myEEPROM.wifiConf = myEEPROM.readWIFIConfig();

    tft.begin();                   /* TFT init */
    tft.setRotation(myEEPROM.LCD_Rotation); // 设置屏幕方向
    tft.fillScreen(0x0000);        // 清除屏幕

    TJpgDec.setJpgScale(1);
    TJpgDec.setSwapBytes(true);
    TJpgDec.setCallback(tftOutput);

    analogWrite(LCD_BL_PIN, 255 - myEEPROM.LCD_BL_PWM); /* 设置屏幕亮度 */

    clk.setColorDepth(8);      // 设置颜色深度
    clk.createSprite(200, 16); // 创建200*50的窗口
    clk.fillSprite(BGCOLOR);
    clk.setTextDatum(MC_DATUM); // 设置居中对齐
    clk.setTextColor(TFT_GREEN, BGCOLOR);
    clk.drawString("Connecting to WiFi", 100, 8, 2);
    clk.pushSprite(20, 152); // 设置窗口位置
    clk.deleteSprite();

    clk.createSprite(200, 16);
    clk.setTextDatum(MR_DATUM);
    clk.setTextColor(TFT_WHITE, BGCOLOR);
    clk.drawString(VERSION, 180, 8, 2);
    clk.pushSprite(20, 178);
    clk.deleteSprite();

    /* 连接WIFI */
#if DEBUG_EN
    // Serial.println("正在连接WIFI ");
    // Serial.printf("SSID:%s\r\n", myEEPROM.wifiConf.stassid);
    // Serial.printf("pasword:%s\r\n", myEEPROM.wifiConf.stapsw);

    Serial.printf("正在连接WIFI\r\nSSID:%s\r\npasword:%s\r\n",
                   myEEPROM.wifiConf.stassid,
                   myEEPROM.wifiConf.stapsw);
#endif
    // WiFi.setTxPower(WIFI_POWER_19_5dBm);
    WiFi.begin(myEEPROM.wifiConf.stassid, myEEPROM.wifiConf.stapsw);

    // 如果WiFi连接失败
    while (WiFi.status() != WL_CONNECTED)
    {
        loading(30);

        // WiFi连接失败
        if (loadNum >= 194)
        {
//使能web配网后自动将smartconfig配网失效
#if WM_EN
            webWin();
            webConfig();
#else
            SmartConfig();
#endif
            break;
        }
    }

    clk.createSprite(200, 16); //创建窗口
    clk.fillSprite(BGCOLOR);
    clk.setTextDatum(MC_DATUM);
    clk.setTextColor(TFT_GREEN, BGCOLOR);
    clk.drawString("Get Weather Info", 100, 8, 2);
    clk.pushSprite(20, 152);
    clk.deleteSprite();

    /* WIFI连接成功，保存WIFI信息到EEPROM */
#if DEBUG_EN
    Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str());
    Serial.printf("PSW:%s\r\n", WiFi.psk().c_str());
#endif
    if (!(strcmp(myEEPROM.wifiConf.stassid, WiFi.SSID().c_str()) == 0 \
        && strcmp(myEEPROM.wifiConf.stapsw, WiFi.psk().c_str()) == 0))
    {
        strcpy(myEEPROM.wifiConf.stassid, WiFi.SSID().c_str()); //名称复制
        strcpy(myEEPROM.wifiConf.stapsw, WiFi.psk().c_str());   //密码复制
        myEEPROM.saveWIFIConfig(&myEEPROM.wifiConf);
    }

    /* 联网更新时间 */
#if DEBUG_EN
    Serial.printf("本地IP:%s\r\n", WiFi.localIP().toString().c_str());
    Serial.println(F("启动UDP"));
    Serial.println(F("等待同步..."));
#endif
    wifiReFlash.getNtpTime();
    if (wifiReFlash.ntpTime)
    {
        setTime(wifiReFlash.ntpTime);
    }
#if DEBUG_EN
    Serial.printf("getNtpTime()获取时间戳:%lli\r\n", wifiReFlash.ntpTime);
#endif

    /* 获取城市代码
     * 如果从EEPROM中读取到的城市代码是合法的，则使用EEPROM中的数据
     * 否则，通过ip获取城市代码，并保存到EEPROM中
     */
    switch (selectCityCodeSource)
    {
    case 0:
        myEEPROM.cityCodeNum = myEEPROM.readCityConfig();
        if (myEEPROM.cityCodeNum >= 101000000 && myEEPROM.cityCodeNum <= 102000000)
            cityCode = String(myEEPROM.cityCodeNum);
        else
            cityCode = wifiReFlash.getCityCode();
        break;
    default:
        cityCode = wifiReFlash.getCityCode();
        break;
    }

    wifiReFlash.getCityWeather(cityCode, &weatherInfo); // 获取天气信息
    // WiFi.forceSleepBegin();                            /* 关闭WiFi*/
    WiFi.setSleep(true);

    /* 让动画走完 */
    while (loadNum < 194)
    {
        loading(1);
    }

    delay(500);
    tft.fillScreen(TFT_BLACK); // 清除启动进度条
    weatherData(&weatherInfo);  // 天气信息显示在屏幕上

    /* 绘制温度、湿度图标 */
    TJpgDec.drawJpg(15, 183, temperature, sizeof(temperature));
    TJpgDec.drawJpg(15, 213, humidity, sizeof(humidity));

    /* 设置协程 */
    reflashTimeThread.setInterval(TMS / 10); //设置刷新时间的间隔 100毫秒
    reflashTimeThread.onRun(reflashTime);
    reflashBannerThread.setInterval(myEEPROM.scrollInterval * TMS); //设置滚动字幕间隔
    reflashBannerThread.onRun(scrollBanner);
    reflashAnimateThread.setInterval(TMS / myEEPROM.animateFrameRate); //设置右下角动画帧率
    reflashAnimateThread.onRun(refreshAnimatedImage);
    triggerUpdateWeatherThread.setInterval(myEEPROM.updateWeatherTime * 60 * TMS);
    triggerUpdateWeatherThread.onRun(triggerUpdateWeather);
    triggerSynchronisedTimeThread.setInterval(12 * 60 * 60 * TMS);
    triggerSynchronisedTimeThread.onRun(triggerSynchronisedTime);
    synchroniseTimeOrUpdateWeatherThread.setInterval(TMS);
    synchroniseTimeOrUpdateWeatherThread.onRun(synchronisedTimeoOrUpdateWeather);
    serialSetThread.setInterval(TMS / 10);
    serialSetThread.onRun(serialSet);

    controller.add(&reflashTimeThread);
    controller.add(&reflashBannerThread);
    controller.add(&reflashAnimateThread);
    controller.add(&triggerUpdateWeatherThread);
    controller.add(&triggerSynchronisedTimeThread);
    controller.add(&synchroniseTimeOrUpdateWeatherThread);
    controller.add(&serialSetThread);
}


void loop()
{
    controller.run();
}
